﻿using Timeline.Beans;
using Timeline.Inis;
using Timeline.Utils;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using Windows.Networking.BackgroundTransfer;
using Windows.ApplicationModel;
using Windows.Graphics.Imaging;
using Windows.Media.FaceAnalysis;
using Windows.Storage.Streams;
using Windows.Storage;
using Windows.ApplicationModel.Resources;

namespace Timeline.Providers {
    public class GeneralProvider {
        // 预缓存图片量
        private const int POOL_CACHE = 2;

        // 索引顺序为「回顾」顺序
        // 注：并非都按时间降序排列，因图源配置而异
        protected readonly List<Meta> metas = new List<Meta>();

        // 帐号信息
        protected AccountApi accountApi = null;

        //protected Dictionary<string, int> dicHistory = new Dictionary<string, int>();
        private readonly BackgroundDownloader downloader = new BackgroundDownloader();

        public string Id { set; get; }

        protected bool AppendMetas(List<Meta> metasAdd) {
            List<string> list = metas.Select(t => t.Id).ToList();
            foreach (Meta meta in metasAdd) {
                if (!list.Contains(meta.Id) && meta.IsValid()) {
                    metas.Add(meta);
                }
            }
            return metas.Count > list.Count;
        }

        protected virtual Meta ParseBean(GeneralApiData bean, string titleRule, string captionRule, string order) {
            Meta meta = new Meta {
                Id = bean.Id,
                No = bean.No,
                Uhd = bean.ImgUrl,
                Thumb = bean.ThumbUrl,
                Copyright = bean.CopyrightRich,
                Story = bean.Story?.Trim(),
                CateHow = bean.CateHowId,
                CateWhat = bean.CateWhatId,
                IdGlobalPrefix = bean.RawProvider,
                IdGlobalSuffix = bean.RawId,
                Src = bean.SrcUrl,
                Score = bean.Score,
                Format = bean.Ext
            };
            if (!string.IsNullOrEmpty(titleRule)) {
                meta.Title = titleRule.Replace("{title}", bean.Title?.Trim())
                    .Replace("{order}", order)
                    .Replace("{no}", bean.No.ToString()).Trim();
            }
            if (!string.IsNullOrEmpty(captionRule)) {
                meta.Caption = captionRule.Replace("{title}", bean.Title?.Trim())
                    .Replace("{order}", order)
                    .Replace("{no}", bean.No.ToString()).Trim();
            }
            //DateTime.TryParseExact(bean.RelDate, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out DateTime date);
            if (DateTime.TryParse(bean.RelDate, out DateTime date)) {
                meta.Date = date;
            }
            return meta;
        }

        public int GetCount() {
            return metas.Count;
        }

        public int GetIndex(Meta meta) {
            for (int i = 0; i < metas.Count; ++i) {
                if (metas[i].Id.Equals(meta?.Id)) {
                    return i;
                }
            }
            return -1;
        }

        /// <summary>
        /// 按序返回下一个
        /// 1. 若当前已是最后一个，则返回最后一个
        /// 2. 若列表为空则返回 null
        /// 3. 若当前为 null 或不存在列表中，则返回第一个
        /// </summary>
        /// <param name="meta"></param>
        /// <returns></returns>
        public Meta GetNextMeta(Meta meta) {
            for (int i = 0; i < metas.Count; ++i) {
                if (metas[i].Id.Equals(meta?.Id)) {
                    return metas[Math.Min(i + 1, metas.Count - 1)];
                }
            }
            return metas.Count > 0 ? metas[0] : null;
        }

        /// <summary>
        /// 按序返回上一个
        /// 1. 若当前已是第一个，则返回第一个
        /// 2. 若列表为空则返回 null
        /// 3. 若当前为 null 或不存在列表中，则返回第一个
        /// </summary>
        /// <param name="meta"></param>
        /// <returns></returns>
        public Meta GetPrevMeta(Meta meta) {
            for (int i = 0; i < metas.Count; ++i) {
                if (metas[i].Id.Equals(meta?.Id)) {
                    return metas[Math.Max(i - 1, 0)];
                }
            }
            return metas.Count > 0 ? metas[0] : null;
        }

        public Meta GetMetaByNo(int no) {
            if (no > 0) {
                for (int i = 0; i < metas.Count; ++i) {
                    if (metas[i].No == no) {
                        return metas[i];
                    }
                }
            }
            return metas.Count > 0 ? metas[0] : null;
        }

        public List<Meta> GetMetas() {
            return metas.ToList();
        }

        public int CalcSplitNo() {
            if (metas.Count > 0) {
                return metas[metas.Count - 1].No - 1;
            }
            return 99999999;
        }

        public bool CheckLoadMore(string loadedMetaId) {
            int index = -1;
            for (int i = 0; i < metas.Count; ++i) {
                if (metas[i].Id.Equals(loadedMetaId)) {
                    index = i;
                    break;
                }
            }
            int count_more = index >= 0 ? metas.Count - (index + 1) : metas.Count;
            return count_more == 0;
        }

        public bool CheckPreLoadMore(string loadedMetaId) {
            int index = -1;
            for (int i = 0; i < metas.Count; ++i) {
                if (metas[i].Id.Equals(loadedMetaId)) {
                    index = i;
                    break;
                }
            }
            int count_more = index >= 0 ? metas.Count - (index + 1) : metas.Count;
            return count_more < 2;
        }

        public void ClearMetas() {
            metas.Clear();
        }

        public virtual async Task<bool> LoadData(CancellationToken token, Ini ai, GeneralIni gi, Go go) {
            if (string.IsNullOrEmpty(gi.GalleryApi)) {
                return false;
            }
            string order = "random".Equals(gi.Order) ? go.Seed.ToString() : gi.Order; // 随机排序使用固定种子值，便于分页加载
            string urlApi = gi.GalleryApi.Replace("{order}", order ?? "")
                .Replace("{catehow}", gi.CateHow ?? "")
                .Replace("{catewhat}", gi.CateWhat ?? "")
                .Replace("{topic}", go.FilterTopic ?? "")
                .Replace("{date}", go.CheckFilterEndDate() ? go.FilterEndDate.ToString("yyyyMMdd") : "")
                .Replace("{id}", go.CheckTargetId() ? go.TargetId : "")
                .Replace("{no}", go.CheckSplitNo() ? go.SplitNo.ToString() : "");
            LogUtil.D("LoadData() provider url: " + urlApi);
            try {
                HttpClient client = new HttpClient();
                HttpRequestMessage msgReq = new HttpRequestMessage(HttpMethod.Get, urlApi);
                msgReq.Headers.Add("Timeline-User", ai.UserEncoded());
                msgReq.Headers.Add("Timeline-Pwd", ai.PwdEncrypted());
                msgReq.Headers.Add("Timeline-Device", SysUtil.GetDeviceId());
                msgReq.Headers.Add("Timeline-Client", ai.Client ?? "");
                HttpResponseMessage res = await client.SendAsync(msgReq, token);
                string jsonData = await res.Content.ReadAsStringAsync();
                //LogUtil.D("LoadData() provider data: " + jsonData.Trim());
                GeneralApi api = JsonConvert.DeserializeObject<GeneralApi>(jsonData);
                this.accountApi = api.Auth; // 缓存帐号信息
                if (api.Status != 1) {
                    return false;
                }
                string orderStr = ResourceLoader.GetForCurrentView().GetString("Order_" + gi.Order);
                List<Meta> metasAdd = new List<Meta>();
                if (api.Extend != null && api.Extend?.Status == 1) {
                    foreach (GeneralApiData item in api.Extend.Data) {
                        metasAdd.Add(ParseBean(item, gi.TitleRule, gi.CaptionRule, orderStr));
                    }
                }
                foreach (GeneralApiData item in api.Data) {
                    metasAdd.Add(ParseBean(item, gi.TitleRule, gi.CaptionRule, orderStr));
                }
                AppendMetas(metasAdd);
                return true;
            } catch (Exception e) {
                // 情况1：任务被取消
                // System.Threading.Tasks.TaskCanceledException: A task was canceled.
                LogUtil.E("LoadData() " + e.Message);
            }
            return false;
        }

        public virtual async Task<Meta> CacheAsync(Meta meta, bool calFacePos, CancellationToken token) {
            LogUtil.D("CacheAsync() " + meta?.Uhd);
            if (meta == null) {
                return null;
            }
            // 缓存当前（等待）
            IReadOnlyList<DownloadOperation> downloading = await BackgroundDownloader.GetCurrentDownloadsAsync();
            LogUtil.D("CacheAsync() history " + downloading.Count);
            if (meta.CacheUhd == null) {
                string cacheName = string.Format("{0}-{1}{2}", Id, meta.Id, meta.Format);
                StorageFile cacheFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync(cacheName, CreationCollisionOption.OpenIfExists);
                if ((await cacheFile.GetBasicPropertiesAsync()).Size > 0) { // 已缓存过
                    LogUtil.D("CacheAsync() cache from disk: " + cacheFile.Path);
                    meta.CacheUhd = cacheFile;
                } else if (meta.Uhd != null) { // 未缓存
                    Uri uriUhd = new Uri(meta.Uhd);
                    if (uriUhd.IsFile) { // 开始复制
                        LogUtil.D("CacheAsync() cache from file: " + cacheFile.Path);
                        try {
                            StorageFile srcFile = await StorageFile.GetFileFromPathAsync(meta.Uhd);
                            await srcFile.CopyAndReplaceAsync(cacheFile);
                            meta.CacheUhd = cacheFile;
                        } catch (Exception e) {
                            LogUtil.E("CacheAsync() " + e.Message);
                        }
                    } else { // 开始缓存
                        if (meta.Do == null) { // 从历史中查找任务
                            foreach (DownloadOperation o in downloading) {
                                if (meta.Uhd.Equals(o.RequestedUri)) {
                                    meta.Do = o;
                                } else if (o.Progress.Status == BackgroundTransferStatus.Running) {
                                    try { // 暂停缓存池之外的任务
                                        o.Pause();
                                        LogUtil.D("CacheAsync() pause: " + meta.Uhd);
                                    } catch (Exception e) {
                                        LogUtil.E("CacheAsync() " + e.Message);
                                    }
                                }
                            }
                        }
                        try {
                            long start = DateTime.Now.Ticks;
                            if (meta.Do != null) { // 从历史中恢复任务
                                LogUtil.D("CacheAsync() cache from history: " + meta.Uhd);
                                if (meta.Do.Progress.Status == BackgroundTransferStatus.PausedByApplication) {
                                    meta.Do.Resume();
                                }
                                await meta.Do.AttachAsync().AsTask(token);
                            } else { // 新建下载任务
                                LogUtil.D("CacheAsync() cache from network: " + meta.Uhd);
                                meta.Do = downloader.CreateDownload(uriUhd, cacheFile);
                                await meta.Do.StartAsync().AsTask(token);
                            }
                            LogUtil.D("CacheAsync() " + meta.Uhd + " " + meta.Do.Progress.Status + " " + (int)((DateTime.Now.Ticks - start) / 10000));
                            //Debug.WriteLine(meta.Uhd);
                            //meta.Do.GetResponseInformation().Headers.TryGetValue("Content-Length", out string contentLength);
                            //int.TryParse(contentLength, out int conLen);
                            //Debug.WriteLine(conLen);
                            //Debug.WriteLine((await meta.Do.ResultFile.GetBasicPropertiesAsync()).Size);
                            if (meta.Do.Progress.Status == BackgroundTransferStatus.Completed) {
                                meta.CacheUhd = meta.Do.ResultFile as StorageFile;
                            }
                        } catch (Exception e) {
                            meta.Do = null; // 置空，下次重新下载
                            // 情况1：链接404
                            // System.Exception: 未找到(404)。
                            // 情况2：任务被取消，此时该下载不再存在于 BackgroundDownloader.GetCurrentDownloadsAsync()
                            // System.Threading.Tasks.TaskCanceledException: A task was canceled.
                            LogUtil.E("CacheAsync() " + e.Message);
                        }
                    }
                }
            }
            // 缓存后续（不等待）
            List<Meta> nextMetas = metas.Skip(GetIndex(meta) + 1).Take(POOL_CACHE).ToList(); // Skip Take 不会有溢出问题
            foreach (Meta m in nextMetas) {
                if (token.IsCancellationRequested || m.CacheUhd != null) { // 无需缓存
                    continue;
                }
                string cacheName = string.Format("{0}-{1}{2}", Id, m.Id, m.Format);
                StorageFile cacheFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync(cacheName, CreationCollisionOption.OpenIfExists);
                if ((await cacheFile.GetBasicPropertiesAsync()).Size > 0) { // 已缓存过
                    m.CacheUhd = cacheFile;
                    LogUtil.D("CacheAsync() cache from disk: " + cacheFile.Path);
                } else if (m.Do != null) { // 下载中
                    if (m.Do.Progress.Status == BackgroundTransferStatus.PausedByApplication) {
                        m.Do.Resume();
                    }
                } else if (m.Uhd != null && !new Uri(m.Uhd).IsFile) { // 未下载
                    foreach (DownloadOperation o in downloading) { // 从历史中恢复任务
                        if (m.Uhd.Equals(o.RequestedUri)) {
                            LogUtil.D("CacheAsync() cache from history: " + m.Uhd);
                            m.Do = o;
                            if (m.Do.Progress.Status == BackgroundTransferStatus.PausedByApplication) {
                                m.Do.Resume();
                            }
                            break;
                        }
                    }
                    if (m.Do == null) { // 新建下载任务
                        LogUtil.D("CacheAsync() cache from network: " + m.Uhd);
                        m.Do = downloader.CreateDownload(new Uri(m.Uhd), cacheFile);
                        _ = m.Do.StartAsync();
                    }
                }
            }
            if (meta.CacheUhd != null && !FileUtil.CheckImageOk(meta.CacheUhd.Path)) { // 检查图片完整性
                LogUtil.D("CacheAsync() broken image: " + meta.Uhd);
                if (!new Uri(meta.Uhd).IsFile) { // 仅处理网络请求的图片
                    await meta.CacheUhd.DeleteAsync();
                    meta.CacheUhd = null;
                    meta.Do = null; // 置空，下次重新下载
                }
            }
            // 获取图片尺寸（耗时短）
            if (meta.Dimen.Width == 0 && meta.CacheUhd != null) {
                try {
                    using (IRandomAccessStream stream = await meta.CacheUhd.OpenAsync(FileAccessMode.Read)) {
                        BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
                        meta.Dimen = new Windows.Foundation.Size(decoder.PixelWidth, decoder.PixelHeight);
                    }
                } catch (Exception e) {
                    LogUtil.E("CacheAsync() " + e.Message);
                }
            }
            if (token.IsCancellationRequested) {
                return meta;
            }
            // 检测人脸位置（耗时较长）
            if (calFacePos && meta.FacePos == null && meta.CacheUhd != null && FaceDetector.IsSupported) {
                long start = DateTime.Now.Ticks;
                meta.FacePos = new List<Windows.Foundation.Point>();
                SoftwareBitmap bitmap = null;
                try {
                    using (IRandomAccessStream stream = await meta.CacheUhd.OpenAsync(FileAccessMode.Read)) {
                        BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
                        // TODO: 该行会随机抛出异常 System.Exception: 图像无法识别
                        bitmap = await decoder.GetSoftwareBitmapAsync(decoder.BitmapPixelFormat,
                            BitmapAlphaMode.Premultiplied, new BitmapTransform(),
                            ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage);
                        if (bitmap.BitmapPixelFormat != BitmapPixelFormat.Gray8) {
                            bitmap = SoftwareBitmap.Convert(bitmap, BitmapPixelFormat.Gray8);
                        }
                        if (token.IsCancellationRequested) {
                            return meta;
                        }
                        FaceDetector detector = await FaceDetector.CreateAsync();
                        IList<DetectedFace> faces = await detector.DetectFacesAsync(bitmap);
                        foreach (DetectedFace face in faces) {
                            meta.FacePos.Add(new Windows.Foundation.Point {
                                X = face.FaceBox.X + face.FaceBox.Width / 2.0f,
                                Y = face.FaceBox.Y + face.FaceBox.Height / 2.0f
                            });
                        }
                        LogUtil.D("detect face cost: " + (int)((DateTime.Now.Ticks - start) / 10000));
                    }
                } catch (Exception ex) {
                    LogUtil.E("CacheAsync() " + ex.Message);
                } finally {
                    bitmap?.Dispose();
                }
            }
            return meta;
        }

        public async Task<StorageFile> DownloadAsync(StorageFolder folder, Meta meta, string provider) {
            if (meta?.CacheUhd == null) {
                return null;
            }
            string appName = AppInfo.Current.DisplayInfo.DisplayName;
            string providerFix = provider;
            string imgIdFix = meta.Id;
            if (!string.IsNullOrEmpty(meta.IdGlobalPrefix) && !string.IsNullOrEmpty(meta.IdGlobalSuffix)) {
                providerFix = ResourceLoader.GetForCurrentView().GetString("Provider_" + meta.IdGlobalPrefix);
                imgIdFix = meta.IdGlobalSuffix;
            }
            try {
                string name = string.Format("{0}_{1}_{2}{3}", appName, providerFix, imgIdFix, meta.Format);
                name = FileUtil.MakeValidFileName(name, "");
                return await meta.CacheUhd.CopyAsync(folder, name, NameCollisionOption.ReplaceExisting);
            } catch (Exception e) {
                LogUtil.E("DownloadAsync() " + e.Message);
            }
            return null;
        }

        public AccountApiData GetAuthData() {
            return this.accountApi != null && this.accountApi.Status == 1 ? this.accountApi.Data : null;
        }
    }
}
